﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Godot.core;
using Godot.core.io;

namespace Godot.modules.gdscript
{
    public partial class GDFunctions
    {
        public static void call(Function p_func, Variant[] p_args, ref Variant r_ret, ref Variant.CallError r_err)
        {
            /*if (!Enum.IsDefined(typeof(Function), p_func) && !p_func.Equals(Function.FUNC_MAX))
            {
                r_err.error = Variant.CallError.CALL_ERROR_INVALID_METHOD;
                return;
            }

            r_err.error = Variant.CallError.CALL_ERROR_OK;

            switch (p_func)
            {
                #region DICT2INST
                case Function.FUNC_DICT2INST:
                    if(!p_args[0].type.Equals(Variant.Type.TYPE_DICTIONARY)) 
                    {
				        r_err.error = Variant.CallError.CALL_ERROR_INVALID_ARGUMENT;
				        r_err.argument = 0;
				        r_err.expected = Variant.Type.TYPE_DICTIONARY;
				        return;
			        }

                    GDictionary d = p_args[0];

                    if (!d.has("@path"))
                    {
                        r_err.error = Variant.CallError.CALL_ERROR_INVALID_ARGUMENT;
				        r_err.argument = 0;
				        r_err.expected = Variant.Type.TYPE_OBJECT;
				        return;
                    }

                    Script scr = ResourceLoader.load(d["@path"]) as Script;
                    if (scr == null)
                    {
                        r_err.error = Variant.CallError.CALL_ERROR_INVALID_ARGUMENT;
				        r_err.argument = 0;
				        r_err.expected = Variant.Type.TYPE_OBJECT;
                        return;
                    }

                    GDScript gdscr = scr as GDScript;

                    if (gdscr == null)
                    {
                        r_err.error = Variant.CallError.CALL_ERROR_INVALID_ARGUMENT;
                        r_err.argument = 0;
                        r_err.expected = Variant.Type.TYPE_OBJECT;
                        return;
                    }

                    NodePath sub = new NodePath();
                    if (d.has("@subpath"))
                    {
                        sub = d["@subpath"];
                    }

                    for (int i = 0; i < sub.names.Count; i++)
                    {
                        gdscr = gdscr.subclasses[sub.names[i]] as GDScript;
                        
                        if (gdscr == null)
                        {
                            r_err.error = Variant.CallError.CALL_ERROR_INVALID_ARGUMENT;
					        r_err.argument = 0;
					        r_err.expected = Variant.Type.TYPE_OBJECT;
					        return;
                        }
                    }

                    r_ret = gdscr.new_instance(null, r_err);
                    break;
                #endregion

                #region GEN_RANGE
                case Function.FUNC_GEN_RANGE:
                    switch (p_args.Length)
                    {
                        case 0:
                            r_err.error = Variant.CallError.CALL_ERROR_TOO_FEW_ARGUMENTS;
                            r_err.argument = 1;
                            break;

                    }
                    //UNIMPLEMENTED
                    break;
                #endregion

                #region INST2DICT
                case Function.FUNC_INST2DICT:
                    if (p_args[0].type == Variant.Type.TYPE_NIL) 
                    {
				        Variant.reset(r_ret);
			        } 
                    else if (p_args[0].type != Variant.Type.TYPE_OBJECT) 
                    {
				        r_err.error = Variant.CallError.CALL_ERROR_INVALID_ARGUMENT;
				        r_err.argument = 0;
				        Variant.reset(r_ret);
			        } 
                    else
                    {
                        GObject obj = p_args[0];
				        if (obj == null) 
                        {
					        Variant.reset(r_ret);
				        } 
                        else if (obj.script_instance != null 
                        || obj.script_instance.language != GDScriptLanguage.singleton) 
                        {
					        r_err.error = Variant.CallError.CALL_ERROR_INVALID_ARGUMENT;
					        r_err.argument = 0;
					        r_err.expected = Variant.Type.TYPE_DICTIONARY;
                            Variant.reset(r_ret);

				        } 
                        else
                        {
                            GDInstance ins = obj.script_instance as GDInstance;
					        GDScript base_scr = ins.script as GDScript;
					        if (base_scr == null) 
                            {
						        r_err.error = Variant.CallError.CALL_ERROR_INVALID_ARGUMENT;
						        r_err.argument = 0;
						        r_err.expected = Variant.Type.TYPE_DICTIONARY;
						        Variant.reset(r_ret);
						        return;
					        }   
					        
                            List<StringName> sname = new List<StringName>();

                            GDScript p = base_scr;

					        while(p.owner != null) {

						        sname.Add(p.name);
						        p = p.owner;
					        }
					        sname.Reverse();


					        if (!p.path.is_resource_file()) 
                            {
						        r_err.error = Variant.CallError.CALL_ERROR_INVALID_ARGUMENT;
						        r_err.argument = 0;
						        r_err.expected = Variant.Type.TYPE_DICTIONARY;
						        return;
					        }

					        NodePath cp = new NodePath(sname, new List<StringName>(), false);

					        GDictionary dict = new GDictionary();
					        dict["@subpath"] = cp;
					        dict["@path"] =  p.path;


					        p = base_scr;

					        while(p != null) 
                            {

						        for(Set<StringName>.Element E = p.members.front; E != null ; E = E.next) 
                                {

							        Variant value  = new Variant();
							        if (ins.get(E.value, value)) 
                                    {
								        String k = E.value;
								        if (!dict.has(k)) 
                                        {
									        dict[k] = value;
								        }
							        }
						        }
                                
						        p = p.basescr;
					        }

					        r_ret = dict;
                        }
                    }
                    break;
                #endregion

                #region LOGIC_CLAMP
                case Function.FUNC_LOGIC_CLAMP:
                    if (p_args[0].type.Equals(Variant.Type.TYPE_INT) 
                    && p_args[1].type.Equals(Variant.Type.TYPE_INT) 
                    && p_args[2].type.Equals(Variant.Type.TYPE_INT)) 
                    {
				        long a = p_args[0];
				        long b = p_args[1];
				        long c = p_args[2];
				        r_ret = Misc.clamp(a, b, c);
			        }
                    else
                    {
				        double a = p_args[0];
				        double b = p_args[1];
				        double c = p_args[2];

				        r_ret = Misc.clamp(a, b, c);
			        }
                    break;
                #endregion

                #region LOGIC_MAX
                case Function.FUNC_LOGIC_MAX:
                    if (p_args[0].type.Equals(Variant.Type.TYPE_INT) 
                    && p_args[1].type.Equals(Variant.Type.TYPE_INT)) 
                    {
				        long a = p_args[0];
				        long b = p_args[1];
				        r_ret = Math.Max(a, b);
			        }
                    else
                    {
				        double a = p_args[0];
				        double b = p_args[1];

				        r_ret = Math.Max(a, b);
			        }
                    break;
                #endregion

                #region LOGIC_MIN
                case Function.FUNC_LOGIC_MIN:
                    if (p_args[0].type.Equals(Variant.Type.TYPE_INT) 
                    && p_args[1].type.Equals(Variant.Type.TYPE_INT)) 
                    {
				        long a = p_args[0];
				        long b = p_args[1];
				        r_ret = Math.Min(a, b);
			        }
                    else
                    {
				        double a = p_args[0];
				        double b = p_args[1];

				        r_ret = Math.Min(a, b);
			        }
                    break;
                #endregion

                #region LOGIC_NEAREST_PO2
                case Function.FUNC_LOGIC_NEAREST_PO2:
			        long num = p_args[0];
			        r_ret =  (long) Misc.nearest_power_of_2((uint)num);
                    break;
                #endregion

                #region MATH_ABS
                case Function.FUNC_MATH_ABS:
                    if (p_args[0].type.Equals(Variant.Type.TYPE_INT))
                    {
                        long num1 = p_args[0];
                        r_ret = num1 < 0 ? -num1 : num1;
                    }
                    else if (p_args[0].type.Equals(Variant.Type.TYPE_REAL))
                    {
                        r_ret = Math.Abs((double)p_args[0]);
                    }
                    else
                    {
                        r_err.error = Variant.CallError.CALL_ERROR_INVALID_ARGUMENT;
                        r_err.argument = 0;
                        r_err.expected = Variant.Type.TYPE_REAL;
                    }
                    break;
                #endregion

                #region MATH_ACOS
                case Function.FUNC_MATH_ACOS:
                    r_ret = Math.Acos(p_args[0]);
                    break;
                #endregion

                #region MATH_ASIN
                case Function.FUNC_MATH_ASIN:
                    r_ret = Math.Asin(p_args[0]);
                    break;
                #endregion

                #region MATH_ATAN
                case Function.FUNC_MATH_ATAN:
                    r_ret = Math.Atan(p_args[0]);
                    break;
                #endregion

                #region MATH_ATAN2
                case Function.FUNC_MATH_ATAN2:
                    r_ret = Math.Atan2(p_args[0], p_args[1]);
                    break;
                #endregion

                #region MATH_CEIL
                case Function.FUNC_MATH_CEIL:
                    r_ret = Math.Ceiling((double)p_args[0]);
                    break;
                #endregion

                #region MATH_COS
                case Function.FUNC_MATH_COS:
                    r_ret = Math.Cos(p_args[0]);
                    break;
                #endregion

                #region MATH_COSH
                case Function.FUNC_MATH_COSH:
                    r_ret = Math.Cosh(p_args[0]);
                    break;
                #endregion

                #region MATH_DB2LINEAR
                case Function.FUNC_MATH_DB2LINEAR:
                    r_ret = Misc.db2linear(p_args[0]);
                    break;
                #endregion

                #region MATH_DECIMALS
                case Function.FUNC_MATH_DECIMALS:
                    r_ret = Misc.decimals(p_args[0]);
                    break;
                #endregion

                #region MATH_DECTIME
                case Function.FUNC_MATH_DECTIME:
                    r_ret = Misc.dectime(p_args[0], p_args[1], p_args[2]);
                    break;
                #endregion

                #region MATH_DEG2RAD
                case Function.FUNC_MATH_DEG2RAD:
                    r_ret = Misc.deg2rad(p_args[0]);
                    break;
                #endregion

                #region MATH_EASE
                case Function.FUNC_MATH_EASE:
                    r_ret = Misc.ease(p_args[0], p_args[1]);
                    break;
                #endregion

                #region MATH_EXP
                case Function.FUNC_MATH_EXP:
                    r_ret = Math.Exp(p_args[0]);
                    break;
                #endregion

                #region MATH_FLOOR
                case Function.FUNC_MATH_FLOOR:
                    r_ret = Math.Floor((double)p_args[0]);
                    break;
                #endregion

                #region MATH_FMOD
                case Function.FUNC_MATH_FMOD:
                    r_ret = Math.IEEERemainder(p_args[0], p_args[1]);
                    break;
                #endregion

                #region MATH_FPOSMOD
                case Function.FUNC_MATH_FPOSMOD:
                    r_ret = Misc.fposmod(p_args[0], p_args[1]);
                    break;
                #endregion

                #region MATH_ISINF
                case Function.FUNC_MATH_ISINF:
                    r_ret = double.IsInfinity(p_args[0]);
                    break;
                #endregion

                #region MATH_ISNAN
                case Function.FUNC_MATH_ISNAN:
                    r_ret = double.IsNaN(p_args[0]);
                    break;
                #endregion

                #region MATH_LERP
                case Function.FUNC_MATH_LERP:
                    r_ret = Misc.lerp(p_args[0], p_args[1], p_args[2]);
                    break;
                #endregion

                #region MATH_LINEAR2DB
                case Function.FUNC_MATH_LINEAR2DB:
                    r_ret = Misc.linear2db(p_args[0]);
                    break;
                #endregion

                #region MATH_LOG
                case Function.FUNC_MATH_LOG:
                    r_ret = Math.Log(p_args[0]);
                    break;
                #endregion

                #region MATH_POW
                case Function.FUNC_MATH_POW:
                    r_ret = Math.Pow(p_args[0], p_args[1]);
                    break;
                #endregion

                #region MATH_RAD2DEG
                case  Function.FUNC_MATH_RAD2DEG:
                    r_ret = Misc.rad2deg(p_args[0]);
                    break;
                #endregion

                case Function.FUNC_MATH_RAND:
                    //UNIMPLEMENTED
                    break;

                case Function.FUNC_MATH_RANDF:
                    //UNIMPLEMENTED
                    break;

                case Function.FUNC_MATH_RANDOM:
                    //UNIMPLEMENTED
                    break;

                case Function.FUNC_MATH_RANDSEED:
                    //UNIMPLEMENTED
                    break;

                #region MATH_ROUND
                case Function.FUNC_MATH_ROUND:
                    r_ret = Math.Round((double)p_args[0], MidpointRounding.ToEven);
                    break;
                #endregion

                #region MATH_SIGN
                case Function.FUNC_MATH_SIGN:
				    if (p_args[0].type.Equals(Variant.Type.TYPE_INT))
                    {
					    long i = p_args[0];
					    r_ret= i < 0 ? -1 : ( i > 0 ? +1 : 0);
				    } 
                    else if (p_args[0].type.Equals(Variant.Type.TYPE_REAL)) 
                    {

					    double r = p_args[0];
					    r_ret= r < 0.0 ? -1.0 : (r > 0.0 ? +1.0 : 0.0);
				    } 
                    else 
                    {
					    r_err.error = Variant.CallError.CALL_ERROR_INVALID_ARGUMENT;
					    r_err.argument = 0;
					    r_err.expected = Variant.Type.TYPE_REAL;
				    }
                    break;
                #endregion

                #region MATH_SIN
                case Function.FUNC_MATH_SIN:
                    r_ret = Math.Sin(p_args[0]);
                    break;
                #endregion

                #region MATH_SINH
                case Function.FUNC_MATH_SINH:
                    r_ret = Math.Sinh(p_args[0]);
                    break;
                #endregion

                #region MATH_SQRT
                case Function.FUNC_MATH_SQRT:
                    r_ret = Math.Sqrt(p_args[0]);
                    break;
                #endregion

                #region MATH_STEPIFY
                case Function.FUNC_MATH_STEPIFY:
                    r_ret = Misc.stepify(p_args[0], p_args[1]);
                    break;
                #endregion

                #region MATH_TAN
                case Function.FUNC_MATH_TAN:
                    r_ret = Math.Tan(p_args[0]);
                    break;
                #endregion

                #region MATH_TANH
                case Function.FUNC_MATH_TANH:
                    r_ret = Math.Tanh(p_args[0]);
                    break;
                #endregion

                #region OBJ_WEAKREF
                case Function.FUNC_OBJ_WEAKREF:
                    r_ret = p_args[0];
                    break;
                #endregion

                case Function.FUNC_PRINT_STACK:
                    //UNIMPLEMENTED
                    break;

                #region RESOURCE_LOAD
                case Function.FUNC_RESOURCE_LOAD:
			        if (p_args[0].type != Variant.Type.TYPE_STRING) 
                    {
				        r_err.error = Variant.CallError.CALL_ERROR_INVALID_ARGUMENT;
				        r_err.argument = 0;
			        }
			        r_ret = ResourceLoader.load(p_args[0]);
                    break;
                #endregion

                case Function.FUNC_TEXT_PRINT:
                    //UNIMPLEMENTED
                    break;

                case Function.FUNC_TEXT_PRINT_TABBED:
                    //UNIMPLEMENTED
                    break;

                case Function.FUNC_TEXT_PRINTERR:
                    //UNIMPLEMENTED
                    break;

                case Function.FUNC_TEXT_PRINTRAW:
                    //UNIMPLEMENTED
                    break;

                #region TEXT_STR
                case Function.FUNC_TEXT_STR:     
			        String str = "";
			        for(int i = 0; i < p_args.Length; i++)
                        str += (String)p_args[i];
			        r_ret=str;
                    break;
                #endregion

                #region TYPE_CONVERT
                case Function.FUNC_TYPE_CONVERT:
                    Variant.Type ct = (Variant.Type) (int)p_args[1];
			        if (!Enum.IsDefined(typeof(Variant.Type), ct) && !ct.Equals(Variant.Type.TYPE_MAX)) 
                    {
				        r_ret = (int)Variant.Type.TYPE_NIL;
			        } 
                    else 
                    {
				        r_ret = Variant.construct(ct, p_args, r_err);
			        }
                    break;
                #endregion

                #region TYPE_OF
                case Function.FUNC_TYPE_OF:
                    r_ret = (int)p_args[0].type;
                    break;
                #endregion

            }*/
        }

        public static bool is_deterministic(Function p_func) 
        {
            //man i couldn't have chosen a worse function name,
            //way too controversial..

            switch (p_func)
            {

                case GDFunctions.Function.MATH_SIN:
                case GDFunctions.Function.MATH_COS:
                case GDFunctions.Function.MATH_TAN:
                case GDFunctions.Function.MATH_SINH:
                case GDFunctions.Function.MATH_COSH:
                case GDFunctions.Function.MATH_TANH:
                case GDFunctions.Function.MATH_ASIN:
                case GDFunctions.Function.MATH_ACOS:
                case GDFunctions.Function.MATH_ATAN:
                case GDFunctions.Function.MATH_ATAN2:
                case GDFunctions.Function.MATH_SQRT:
                case GDFunctions.Function.MATH_FMOD:
                case GDFunctions.Function.MATH_FPOSMOD:
                case GDFunctions.Function.MATH_FLOOR:
                case GDFunctions.Function.MATH_CEIL:
                case GDFunctions.Function.MATH_ROUND:
                case GDFunctions.Function.MATH_ABS:
                case GDFunctions.Function.MATH_SIGN:
                case GDFunctions.Function.MATH_POW:
                case GDFunctions.Function.MATH_LOG:
                case GDFunctions.Function.MATH_EXP:
                case GDFunctions.Function.MATH_ISNAN:
                case GDFunctions.Function.MATH_ISINF:
                case GDFunctions.Function.MATH_EASE:
                case GDFunctions.Function.MATH_DECIMALS:
                case GDFunctions.Function.MATH_STEPIFY:
                case GDFunctions.Function.MATH_LERP:
                case GDFunctions.Function.MATH_DECTIME:
                case GDFunctions.Function.MATH_DEG2RAD:
                case GDFunctions.Function.MATH_RAD2DEG:
                case GDFunctions.Function.MATH_LINEAR2DB:
                case GDFunctions.Function.MATH_DB2LINEAR:
                case GDFunctions.Function.LOGIC_MAX:
                case GDFunctions.Function.LOGIC_MIN:
                case GDFunctions.Function.LOGIC_CLAMP:
                case GDFunctions.Function.LOGIC_NEAREST_PO2:
                case GDFunctions.Function.TYPE_CONVERT:
                case GDFunctions.Function.TYPE_OF:
                case GDFunctions.Function.TEXT_STR:
                    // enable for debug only, otherwise not desirable - case GEN_RANGE:
                    return true;
                default:
                    return false;

            }

            return false;
        }

        //<TOOLS_ENABLED> public static MethodInfo get_info(Function p_func) { }
    }
}
